﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace AGR.Security.Contract
{
    public class Cryptography : ICryptography
    {
        #region Constructor
        public Cryptography()
        { }
        #endregion

        #region Private Methods

        private byte[] Salt()
        {
            return Encoding.ASCII.GetBytes("{4DC071F3-6AEB-4127-B22A-13B97D575105}");
        }

        private string GenerateNewSalt()
        {
            return Guid.NewGuid().ToString();
        }

        private static byte[] ReadByteArray(Stream s)
        {
            byte[] rawLength = new byte[sizeof(int)];
            if (s.Read(rawLength, 0, rawLength.Length) != rawLength.Length)
            {
                throw new SystemException("Stream did not contain properly formatted byte array");
            }

            byte[] buffer = new byte[BitConverter.ToInt32(rawLength, 0)];
            if (s.Read(buffer, 0, buffer.Length) != buffer.Length)
            {
                throw new SystemException("Did not read byte array properly");
            }

            return buffer;
        }

        #endregion

        #region Contract Methods

        public string EncryptMessage(string password, string message)
        {
            if (string.IsNullOrEmpty(message))
                throw new ArgumentNullException("message");
            if (string.IsNullOrEmpty(password))
                throw new ArgumentNullException("password");

            string encryptedMessage = null;
            RijndaelManaged aesAlg = null;

            byte[] salt = Salt();

            try
            {
                Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt);

                aesAlg = new RijndaelManaged();
                aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);

                ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    msEncrypt.Write(BitConverter.GetBytes(aesAlg.IV.Length), 0, sizeof(int));
                    msEncrypt.Write(aesAlg.IV, 0, aesAlg.IV.Length);

                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                        {
                            swEncrypt.Write(message);
                        }
                    }

                    encryptedMessage = Convert.ToBase64String(msEncrypt.ToArray());
                }
            }
            finally
            {
                if (aesAlg != null)
                    aesAlg.Clear();
            }

            return encryptedMessage;
        }

        public string DecryptMessage(string password, string encryptedMessage)
        {
            if (string.IsNullOrEmpty(encryptedMessage))
                throw new ArgumentNullException("encryptedMessage");
            if (string.IsNullOrEmpty(password))
                throw new ArgumentNullException("pasword");

            RijndaelManaged aesAlg = null;
            string message = null;

            byte[] salt = Salt();

            try
            {
                Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt);
            
                byte[] bytes = Convert.FromBase64String(encryptedMessage);

                using (MemoryStream msDecrypt = new MemoryStream(bytes))
                {
                    aesAlg = new RijndaelManaged();
                    aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);

                    aesAlg.IV = ReadByteArray(msDecrypt);

                    ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                            message = srDecrypt.ReadToEnd();
                    }
                }
            }
            finally
            {
                if (aesAlg != null)
                    aesAlg.Clear();
            }

            return message;
        }

        public Guid GenerateSeqeuntialGuid(SequentialGuidType guidType)
        {
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

            byte[] randomBytes = new byte[10];
            rng.GetBytes(randomBytes);

            long timestamp = DateTime.Now.Ticks / 10000L;
            byte[] timestampBytes = BitConverter.GetBytes(timestamp);

            if (BitConverter.IsLittleEndian)
                Array.Reverse(timestampBytes);

            byte[] guidBytes = new byte[16];

            switch (guidType)
            {
                case SequentialGuidType.SequentialAsString:
                case SequentialGuidType.SequentialAsBinary:

                    Buffer.BlockCopy(timestampBytes, 2, guidBytes, 0, 6);
                    Buffer.BlockCopy(randomBytes, 0, guidBytes, 6, 10);

                    // If formatting as a string, we have to reverse the order of the Data1 and Data2 blocks on little-endian systems.
                    if (guidType == SequentialGuidType.SequentialAsString && BitConverter.IsLittleEndian)
                    {
                        Array.Reverse(guidBytes, 0, 4);
                        Array.Reverse(guidBytes, 4, 2);
                    }
                    break;

                case SequentialGuidType.SequentialAtEnd:

                    Buffer.BlockCopy(randomBytes, 0, guidBytes, 0, 10);
                    Buffer.BlockCopy(timestampBytes, 2, guidBytes, 10, 6);
                    break;
            }

            return new Guid(guidBytes);
        }

        #endregion

        #region Dispose
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // get rid of managed resources
            }
            // get rid of unmanaged resources 
        }
        #endregion
    }
}